package eu.whowie.bluecontrol.player;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import eu.whowie.bluecontrol.player.VlcFSM.Events;

public class VlcHandler {

	private final VlcFSM vlcFSM;
	private final OutputProcessor stdin;
	private final CommandProcessor stdout;
	private final ErrorProcessor stderr;

	private final Logger logger = LoggerFactory.getLogger(VlcHandler.class);
	private final Process vlcProcess;

	private boolean ready = false;
	private final boolean isShutDown = false;

	public VlcHandler(String[] cmd) throws IOException {
		vlcFSM = new VlcFSM(this);
		vlcProcess = Runtime.getRuntime().exec(cmd);
		stdin = new OutputProcessor(vlcProcess.getInputStream(), vlcFSM);
		stdout = new CommandProcessor(vlcProcess.getOutputStream(), vlcProcess);
		stderr = new ErrorProcessor(vlcProcess.getErrorStream());
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < cmd.length; i++) {
			builder.append(cmd[i]);
			builder.append(" ");
		}

		logger.info("started VLC with cmd: " + builder.toString());
	}

	public void shutdown() {
		vlcFSM.process(VlcFSM.Events.SHUTDOWN);
	}

	public void test() {
		vlcFSM.process(VlcFSM.Events.TEST);

	}

	public void runCmd(String cmd) {
		logger.debug("running command {}", cmd);
		try {
			stdout.runCommand(cmd);
		} catch (FailToSendCommandException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public boolean isShuttedDown() {
		return isShutDown;

	}

	public String getStatus() {
		return vlcFSM.getCurrentState().toString();
	}

	public boolean isReady() {
		return ready;
	}

	public void setReady(boolean ready) {
		this.ready = ready;
	}

}

class OutputProcessor implements Runnable {
	Logger logger = LoggerFactory.getLogger(OutputProcessor.class);
	private final InputStream inputStream;

	Pattern INITIALIZED_PAT = Pattern
			.compile("Command Line Interface initialized.*");
	private final VlcFSM fsm;

	public OutputProcessor(InputStream inputStream, VlcFSM fsm) {
		this.inputStream = inputStream;
		this.fsm = fsm;
		new Thread(this).start();
	}

	@Override
	public void run() {
		BufferedReader br = new BufferedReader(new InputStreamReader(
				inputStream));

		String line = null;
		try {
			while ((line = br.readLine()) != null) {
				logger.debug(">>>>> : {}", line);

				if (INITIALIZED_PAT.matcher(line).matches()) {
					logger.debug("init pattern found");
					fsm.process(Events.INITIALIZED);
				}

			}
		} catch (IOException e) {
			logger.error("cant read from inputStream");
			e.printStackTrace();
		}
	}
}

class ErrorProcessor implements Runnable {
	Logger logger = LoggerFactory.getLogger(OutputProcessor.class);
	private final InputStream inputStream;

	public ErrorProcessor(InputStream inputStream) {
		this.inputStream = inputStream;
		new Thread(this).start();
	}

	@Override
	public void run() {
		BufferedReader br = new BufferedReader(new InputStreamReader(
				inputStream));

		String line = null;
		try {
			while ((line = br.readLine()) != null) {
				logger.debug("[ERR]: {}", line);
			}
		} catch (IOException e) {
			logger.error("cant read from inputStream");
			e.printStackTrace();
		}
	}
}

class CommandProcessor implements Runnable {
	final int MAX_CAPACITY = 10;
	final int TIMEOUT = 50;

	private final BlockingQueue<String> queue = new ArrayBlockingQueue<String>(
			MAX_CAPACITY);
	private final OutputStream outputStream;
	private final Logger logger = LoggerFactory
			.getLogger(CommandProcessor.class);
	private final Process p;

	public CommandProcessor(OutputStream outputStream, Process p) {
		this.outputStream = outputStream;
		this.p = p;
		new Thread(this).start();
	}

	public void runCommand(String cmd) throws FailToSendCommandException {
		boolean accepted = false;
		try {
			accepted = queue.offer(cmd, TIMEOUT, TimeUnit.MILLISECONDS);
		} catch (InterruptedException e) {
			throw new FailToSendCommandException("interupted");
		}
		if (!accepted) {
			throw new FailToSendCommandException("queue full");
		}
	}

	@Override
	public void run() {
		String cmd = null;
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
				outputStream));
		try {
			while ((cmd = queue.take()) != null) {
				logger.debug("writing cmd({}) to vlc socket", cmd);
				bw.write(cmd);
				bw.write("\n");
				bw.flush();

			}
		} catch (InterruptedException e) {
			logger.error("interupted while writing cmd {} ", cmd);
		} catch (IOException e) {
			logger.error("fail to write cmd: {}", cmd);
			logger.error("proc is alive? " + p.exitValue());
		}

	}
}